OpenXR Extension Api Layer实现方案

OpenXR Extension Api Layer实现方案

OpenXR中的Extension有两种实现方式,第一种方式是直接以Hard Code方式直接写在Runtime中,第二种方式则是以Api layer的方式来“外挂实现”。这边主要介绍api layer的实现方式为主。

Api Layer简介

Khronos在介绍Api Layer的时候给出的例子有点过于具体了,以至于刚开始我也就认为Api layer是实现debug + validating。

Some examples of features that API layers may expose include:

  • Validating API usage
  • Adding the ability to perform API tracing and debugging
  • Intercept and filter information between the application and the runtime

但实际上,在整个Api layer介绍的开头,Khronos已经是给出了一个定义:

API layers are optional components that augment the OpenXR system.

然后也给出了一个功能和架构的介绍:

They may intercept, evaluate, modify, and insert existing OpenXR commands on their way from the application down to the runtime.

最后是给出了一个实施的建议:

API layers are implemented as libraries that are enabled in a variety of ways (including by application request).

以上这一大段的内容出自:https://www.khronos.org/registry/OpenXR/specs/1.0/loader.html#openxr-api-layers

其实概括起来,api layer是作为Runtime的一个增强组件而存在,主要是以so库的形式给openxr command提供一些额外的能力,这些能力可以是debug信息,validating,甚至是hook。有了以上的认知,我们再来看整个api layer中的call flow,感觉就会好一些:

image-20220622104405039

Api Layer的实现

Api Layer的实现分为两个步骤,一个是业务/功能代码编写,第二个是loader对于so库的加载。

Api Layer的代码编写

Khronos本身针对api layer其实是有写了sample code,但是这个sample code具有一定的迷惑性,因为它是做了debug和validating两个样例。

github的下载地址:https://github.com/KhronosGroup/OpenXR-SDK-Source

当我们克隆这份代码以后,有一个比较简单的方式来编译,就是借助hello_xr的工程来做api layer的编译。

为了避免歧义,我这边以commit:915196a27819257b513aae85985a129715d8cb7e为例来进行整个流程的演示:

https://github.com/KhronosGroup/OpenXR-SDK-Source/tree/915196a27819257b513aae85985a129715d8cb7e

api layer的编译

由于我们是借助hello_xr的项目来进行编译,所以先要确定如何把api layer编进去,所以需要先去api_layers目录下找到具体的target

https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/915196a27819257b513aae85985a129715d8cb7e/src/api_layers/CMakeLists.txt#L143

1
target_link_libraries(XrApiLayer_core_validation PRIVATE Threads::Threads)

得到了对应的target:XrApiLayer_core_validation,再回到hello_xrbuild.gradle中,我们需要增加该target

https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/915196a27819257b513aae85985a129715d8cb7e/src/tests/hello_xr/build.gradle#L44-L50

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
android {
......
defaultConfig {
......
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_shared',
'-DBUILD_API_LAYERS=ON',
'-DBUILD_TESTS=ON',
'-DBUILD_LOADER=ON',
'-DBUILD_CONFORMANCE_TESTS=OFF',
'-DBUILD_ALL_EXTENSIONS=ON'
targets "openxr_loader", "hello_xr", "XrApiLayer_core_validation"
}
}
}

需要打开'-DBUILD_API_LAYERS=ON',以及增加target:XrApiLayer_core_validation

image-20220622104854877

这样以后直接编译hello_xr,就可以了连带编译出libXrApiLayer_core_validation.so,这样可以达到api layer编译的目的了。

业务代码的编写

由于实际业务中,我们是想要支持某个extension,而这个extension的实现并不是在runtime中做的,比如说支持XR_EXT_hand_tracking,那么我们就可以在api layer中去实现xrCreateHandTrackerEXT等等的函数,其他的特性,诸如XR_FB_passthrough等等的也是同理。

假定我们需要通过api layer去实现XR_EXT_hand_tracking,我们借用XrApiLayer_core_validation的代码来简单实现:xrCreateHandTrackerEXT

  • 首先是修改validation_layer_generator.py,在这里增加我们想要hook的函数:xrCreateHandTrackerEXT

https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/915196a27819257b513aae85985a129715d8cb7e/src/scripts/validation_layer_generator.py#L31-L42

img

1
2
3
4
5
6
# The following commands have a manually defined component to them.
VALID_USAGE_MANUALLY_DEFINED = set((
......
'xrCreateHandTrackerEXT',
......
))
  • 在增加了这个代码后,编译过程中会生成:

    OpenXR-SDK-Source/src/tests/hello_xr/.cxx/cmake/OpenGLESDebug/arm64-v8a/src/api_layers/xr_generated_core_validation.cpp

1
2
3
4
5
6
7
8
9
10
11
static PFN_xrVoidFunction GenValidUsageInnerGetInstanceProcAddr(
const char* name) {
std::string func_name = name;
......
// ---- XR_EXT_hand_tracking extension commands
if (func_name == "xrCreateHandTrackerEXT") {
return reinterpret_cast<PFN_xrVoidFunction>(CoreValidationXrCreateHandTrackerEXT);
}
......
return nullptr;
}
  • 接着再来看一下对于这支api的使用:

    https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/915196a27819257b513aae85985a129715d8cb7e/src/api_layers/core_validation.cpp#L848

    1
    2
    3
    4
    5
    6
    7
    8
    9
    LAYER_EXPORT XrResult xrNegotiateLoaderApiLayerInterface(const XrNegotiateLoaderInfo *loaderInfo, const char * /*apiLayerName*/,
    XrNegotiateApiLayerRequest *apiLayerRequest) {
    .......
    apiLayerRequest->getInstanceProcAddr = reinterpret_cast<PFN_xrGetInstanceProcAddr>(GenValidUsageXrGetInstanceProcAddr);
    apiLayerRequest->createApiLayerInstance =
    reinterpret_cast<PFN_xrCreateApiLayerInstance>(CoreValidationXrCreateApiLayerInstance);
    .......
    return XR_SUCCESS;
    }

    本身getInstanceProcAddr的作用就是寻找库内的函数指针,所以通过这个函数,我们就可以找到api layer so中的某个函数了。

  • 最后是需要实现一下:CoreValidationXrCreateHandTrackerEXT,具体的实现可以放在:

    https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/915196a27819257b513aae85985a129715d8cb7e/src/api_layers/core_validation.cpp

    1
    2
    3
    4
    5
    6
    7
    XRAPI_ATTR XrResult XRAPI_CALL CoreValidationXrCreateHandTrackerEXT(
    XrSession session,
    const XrHandTrackerCreateInfoEXT* createInfo,
    XrHandTrackerEXT* handTracker) {
    ALOGE("%s,%s,%d",__FILE__,__func__,__LINE__);
    return XR_SUCCESS;
    }

    根据call flow,如果需要继续往下调用到runtime或者是其他的api layer的部分,那么代码就要改成:

    1
    2
    3
    4
    5
    6
    7
    XRAPI_ATTR XrResult XRAPI_CALL CoreValidationXrCreateHandTrackerEXT(
    XrSession session,
    const XrHandTrackerCreateInfoEXT* createInfo,
    XrHandTrackerEXT* handTracker) {
    ALOGE("%s,%s,%d",__FILE__,__func__,__LINE__);
    return GenValidUsageNextXrCreateHandTrackerEXT(session, createInfo, handTracker);
    }
  • 其中GenValidUsageNextXrCreateHandTrackerEXT的代码实际也是通过py文件生成的(这里可以不做考虑)

    OpenXR-SDK-Source/src/tests/hello_xr/.cxx/cmake/OpenGLESDebug/arm64-v8a/src/api_layers/xr_generated_core_validation.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    28815 XRAPI_ATTR XrResult XRAPI_CALL GenValidUsageNextXrCreateHandTrackerEXT(
    28816 XrSession session,
    28817 const XrHandTrackerCreateInfoEXT* createInfo,
    28818 XrHandTrackerEXT* handTracker) {
    28819 XrResult result = XR_SUCCESS;
    28820 try {
    28821 auto info_with_instance = g_session_info.getWithInstanceInfo(session);
    28822 GenValidUsageXrHandleInfo *gen_session_info = info_with_instance.first;
    28823 (void)gen_session_info; // quiet warnings
    28824 GenValidUsageXrInstanceInfo *gen_instance_info = info_with_instance.second;
    28825 result = gen_instance_info->dispatch_table->CreateHandTrackerEXT(session, createInfo, handTracker);
    28826 if (XR_SUCCESS == result && nullptr != handTracker) {
    28827 std::unique_ptr<GenValidUsageXrHandleInfo> handle_info(new GenValidUsageXrHandleInfo());
    28828 handle_info->instance_info = gen_instance_info;
    28829 handle_info->direct_parent_type = XR_OBJECT_TYPE_SESSION;
    28830 handle_info->direct_parent_handle = MakeHandleGeneric(session);
    28831 g_handtrackerext_info.insert(*handTracker, std::move(handle_info));
    28832 }
    28833 } catch (std::bad_alloc&) {
    28834 result = XR_ERROR_OUT_OF_MEMORY;
    28835 } catch (...) {
    28836 result = XR_ERROR_VALIDATION_FAILURE;
    28837 }
    28838 return result;
    28839 }

所以,对于xrCreateHandTrackerEXT的最后中期望,是可以打印出:

1
ALOGE("%s,%s,%d",__FILE__,__func__,__LINE__);

Loader对于api layer的加载

loader的章节中,我们也知道api layer so需要被loader找到的话,需要写入对应的json文件。

这个部分可以参考:https://www.khronos.org/registry/OpenXR/specs/1.0/loader.html#api-layer-discovery, 其中的Example 7的图。

image-20220622104700095

所以,针对这个情况,我们在准备api layer加载的时候,就需要准备:

  • 对应的json文件
  • 上面api layer编译出来的具体so文件

另外,对于api layer来说,又有两个类型:ImplicitExplicit,简单来说,Implicit对于应用开发者来说是透明的,应用开发者可以认为”就是”Runtime提供的;而Explicit是需要应用开发者在XrInstanceCreateInfo中带入,是应用开发者明确需要使用的api layer。

这个部分的内容可以参考:https://www.khronos.org/registry/OpenXR/specs/1.0/loader.html#implicit-vs-explicit-api-layers

因此,针对xrCreateHandTrackerEXT,我们倾向于使用Implicit来实现,以下就是针对这个Implicit的具体做法。

json文件的编写

  • XrApiLayer.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"file_format_version": "1.0.0",
"api_layer": {
"name": "XR_APILAYER_thermal",
"library_path": "/data/app/libXrApiLayer_core_validation.so",
"api_version": "1.0",
"implementation_version": "1",
"description": "OpenXR example extension API Layer",
"instance_extensions": [
{
"name": "XR_EXT_example",
"extension_version": "1"
}
],
"disable_environment": "DISABLE_XR_API_LAYER_TEST_1"
}
}

其中比较重要的参数是:

  • “library_path”: “/data/app/libXrApiLayer_core_validation.so”
  • “disable_environment”: “DISABLE_XR_API_LAYER_TEST_1”
  • “name”: “XR_EXT_example”

其中name的部分最后会是support extension,出现在Available Extensions中。

library_path的部分,是指定了对应的api layer so库的具体地址

disable_environment根据spec的定义,在Implicit的api layer中是一个必选字段,代表的含义:Indicates an environment variable used to disable the implicit API layer.

如果只是demo的话,照抄上述的内容就可以了。

Loader的修改

实测下来,标准Khronos给出来的loader在android上并不会发现api layer,因此需要针对loader的部分做一些小修改。

https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/915196a27819257b513aae85985a129715d8cb7e/src/loader/manifest_file.cpp#L812

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
--- a/src/loader/manifest_file.cpp
+++ b/src/loader/manifest_file.cpp
@@ -800,6 +800,7 @@ XrResult ApiLayerManifestFile::FindManifestFiles(ManifestFileType type,
std::string relative_path;
std::string override_env_var;
std::string registry_location;
+ std::vector<std::string> filenames;
// Add the appropriate top-level folders for the relative path. These should be
// the string "openxr/" followed by the API major version as a string.
@@ -810,6 +811,7 @@ XrResult ApiLayerManifestFile::FindManifestFiles(ManifestFileType type,
case MANIFEST_TYPE_IMPLICIT_API_LAYER:
relative_path += OPENXR_IMPLICIT_API_LAYER_RELATIVE_PATH;
override_env_var = "";
+ filenames.push_back("/system/etc/openxr/1/api_layers/implicit.d/XrApiLayer.json");
#ifdef XR_OS_WINDOWS
registry_location = OPENXR_IMPLICIT_API_LAYER_REGISTRY_LOCATION;
#endif
@@ -827,7 +829,7 @@ XrResult ApiLayerManifestFile::FindManifestFiles(ManifestFileType type,
}
bool override_active = false;
- std::vector<std::string> filenames;
+
ReadDataFilesInSearchPaths(override_env_var, relative_path, override_active, filenames);

实际就是hard code,把implicit json文件放到了:/system/etc/openxr/1/api_layers/implicit.d/XrApiLayer.json

可以修改的更加漂亮,但是我们这边以便捷为主,直接这样写了,而QCOM的OpenXR方案中,实际也是写死的,因为从release出来的文件来看,QCOM方案下需要把json文件放到:/system/etc/openxr/1/api_layers/implicit.d/

工程验证

在完成了以上的任务后,我们会得到三个交付件:

  • hello_xr.apk
    • 这个apk可以使用adb install进行安装
  • libXrApiLayer_core_validation.so
    • push到/data/app/libXrApiLayer_core_validation.so
  • XrApiLayer.json
    • push到/system/etc/openxr/1/api_layers/implicit.d/XrApiLayer.json

接着就可以使用OpenXR Runtime(Monado方案)进行验证了,可以得到如下的信息:

  • 支持的额外扩展:XR_EXT_example”

    image-20220622103300825

  • app端samplecode:

    1
    2
    3
    4
    PFN_xrCreateHandTrackerEXT pfn_xrCreateHandTrackerEXT = nullptr;
    CHECK_XRCMD(xrGetInstanceProcAddr(m_instance, "xrCreateHandTrackerEXT",
    reinterpret_cast<PFN_xrVoidFunction*>(&pfn_xrCreateHandTrackerEXT)));
    pfn_xrCreateHandTrackerEXT(XR_NULL_HANDLE,nullptr,nullptr);

    image-20220622103319896

  • api layer的hook打印

    image-20220622103324990